{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0c35cb06",
   "metadata": {},
   "source": [
    "# Build a Molecular Search Engine in Minutes"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "731eedf7",
   "metadata": {},
   "source": [
    "This notebook illustrates how to build a molecular search engine from scratch [Milvus](https://milvus.io/) and [Towhee](https://towhee.io/). Milvus is the most advanced open-source vector database built for AI applications and supports nearest neighbor embedding search across tens of millions of entries, and Towhee is a framework that provides ETL for unstructured data using SoTA machine learning models.\n",
    "\n",
    "We will go through molecular **similarity search** SMILES. Moreover, we managed to make the core functionality as simple as 10 lines of code with Towhee, so that you can start hacking your own molecular search engine."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4883e577",
   "metadata": {},
   "source": [
    "## Preparations"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "49110b91",
   "metadata": {},
   "source": [
    "### Install Dependencies"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3abf593",
   "metadata": {},
   "source": [
    "First we need to install dependencies such as towhee, rdkit and gradio."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "c9ba3850",
   "metadata": {},
   "outputs": [],
   "source": [
    "! python -m pip install -q towhee towhee.models rdkit-pypi gradio"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ddacd4d",
   "metadata": {},
   "source": [
    "Please make sure that you have started a [Milvus service](https://milvus.io/docs/install_standalone-docker.md). This notebook uses [milvus 2.2.10](https://milvus.io/docs/v2.2.x/install_standalone-docker.md) and [pymilvus 2.2.11](https://milvus.io/docs/release_notes.md#2210)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1bb223f9",
   "metadata": {},
   "outputs": [],
   "source": [
    "! python -m pip install -q pymilvus==2.2.11"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a90db0c5",
   "metadata": {},
   "source": [
    "### Prepare the Data"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0fbd1e75",
   "metadata": {},
   "source": [
    "There is a subset of the [Pubchem dataset](https://ftp.ncbi.nlm.nih.gov/pubchem/Compound/CURRENT-Full/SDF/) (10000 SMILES) used in this demo, everyone can download on [Github](https://github.com/towhee-io/examples/releases/download/data/pubchem_10000.smi)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "d1436a9c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n",
      "                                 Dload  Upload   Total   Spent    Left  Speed\n",
      "  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\n",
      "100  561k  100  561k    0     0   214k      0  0:00:02  0:00:02 --:--:--  391k\n"
     ]
    }
   ],
   "source": [
    "! curl -L https://github.com/towhee-io/examples/releases/download/data/pubchem_10000.smi -O"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bdd8754a",
   "metadata": {},
   "source": [
    "**pubchem_10000.smi**: a file containing SMILES and corresponding ids.\n",
    "\n",
    "Let's take a quick look:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "d652efea",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>smiles</th>\n",
       "      <th>id</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>COc1cc(C=NNC(=O)CC2(C)OCCO2)cc(c1OCc1cccc2c1cc...</td>\n",
       "      <td>2860827</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Fc1ccc(cc1)C=C1C(=O)NC(=S)N(C1=O)c1ccc(cc1Cl)Cl</td>\n",
       "      <td>2860828</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>O=C1NC(=C(C(N1)c1cccc(c1)Br)C(=O)Nc1ccc(cc1)Cl)C</td>\n",
       "      <td>2860829</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>COc1ccc(c(c1)OC)C=C1C(=O)NC(=S)N(C1=O)c1cccc(c...</td>\n",
       "      <td>2860830</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Nc1ccn(n1)c1ccccc1.Cl</td>\n",
       "      <td>2860831</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                              smiles       id\n",
       "0  COc1cc(C=NNC(=O)CC2(C)OCCO2)cc(c1OCc1cccc2c1cc...  2860827\n",
       "1    Fc1ccc(cc1)C=C1C(=O)NC(=S)N(C1=O)c1ccc(cc1Cl)Cl  2860828\n",
       "2   O=C1NC(=C(C(N1)c1cccc(c1)Br)C(=O)Nc1ccc(cc1)Cl)C  2860829\n",
       "3  COc1ccc(c(c1)OC)C=C1C(=O)NC(=S)N(C1=O)c1cccc(c...  2860830\n",
       "4                              Nc1ccn(n1)c1ccccc1.Cl  2860831"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "df = pd.read_csv('pubchem_10000.smi')\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19a8d741",
   "metadata": {},
   "source": [
    "To use the dataset for molecular search, let's first define the dictionary and helper function:\n",
    "- `to_images(input)`: convert the input smiles or results to towhee.Image for display."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "4d98b309",
   "metadata": {},
   "outputs": [],
   "source": [
    "from rdkit.Chem import Draw\n",
    "from rdkit import Chem\n",
    "from towhee.types.image_utils import from_pil\n",
    "\n",
    "def to_images(data):\n",
    "    imgs = []\n",
    "    for smiles in data:\n",
    "        mol = Chem.MolFromSmiles(smiles)\n",
    "        img = from_pil(Draw.MolToImage(mol))\n",
    "        imgs.append(img)\n",
    "    return imgs"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "389de7ed",
   "metadata": {},
   "source": [
    "### Create Milvus Collection"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0da865f",
   "metadata": {},
   "source": [
    "Next to create the `molecular_search` collection in Milvus. It's worth to note that the vector field of this collection is the type of \"BINARY_VECTOR\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "22c19982",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection, utility\n",
    "\n",
    "MILVUS_HOST = '127.0.0.1'\n",
    "MILVUS_PORT = '19530'\n",
    "connections.connect(host=MILVUS_HOST, port=MILVUS_PORT)\n",
    "\n",
    "def create_milvus_collection(collection_name, dim):    \n",
    "    if utility.has_collection(collection_name):\n",
    "        utility.drop_collection(collection_name)\n",
    "    \n",
    "    fields = [\n",
    "    FieldSchema(name='id', dtype=DataType.INT64, descrition='ids', is_primary=True, auto_id=False),\n",
    "    FieldSchema(name='smiles', dtype=DataType.VARCHAR, descrition='SMILES', max_length=500),\n",
    "    FieldSchema(name='embedding', dtype=DataType.BINARY_VECTOR, descrition='embedding vectors', dim=dim)\n",
    "    ]\n",
    "    schema = CollectionSchema(fields=fields, description='molecular similarity search')\n",
    "    collection = Collection(name=collection_name, schema=schema)\n",
    "    \n",
    "    index_params = {\"index_type\": \"BIN_FLAT\", \"params\": {\"nlist\": 1024}, \"metric_type\": \"JACCARD\"}\n",
    "    collection.create_index(field_name=\"embedding\", index_params=index_params)\n",
    "    \n",
    "    return collection\n",
    "\n",
    "collection = create_milvus_collection('molecular_search', 2048)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0956ab8",
   "metadata": {},
   "source": [
    "## Molecular Search Engine"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "143255bb",
   "metadata": {},
   "source": [
    "In this section, we will show how to build our molecular search engine using Milvus. The basic idea behind Molecular Search is to use Towhee to generate molecular fingerprints from SMILES and compare the queried fingerprints with the fingerprints stored in Milvus.\n",
    "\n",
    "We use Towhee, a machine learning framework that allows the creation of data processing pipelines. Towhee also provides predefined operators for implementing insert and query operations in Milvus."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6973709d",
   "metadata": {},
   "source": [
    "### Load Molecular Fingerprint into Milvus"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81e8811d",
   "metadata": {},
   "source": [
    "We first generate fingerprint from SMILES with daylight algorithm and insert the fingerprints into Milvus. Towhee provides a [method-chaining style API](https://towhee.readthedocs.io/en/main/index.html) so that users can assemble a data processing pipeline with operators."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "f814f6e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from towhee import ops, pipe\n",
    "\n",
    "insert_pipe = (pipe.input('df')\n",
    "                   .flat_map('df', ('smiles', 'id'), lambda df: df.values.tolist())\n",
    "                   .map('smiles', 'fp', ops.molecular_fingerprinting.rdkit(algorithm='daylight'))\n",
    "                   .map(('id', 'smiles', 'fp'), 'res', ops.ann_insert.milvus_client(host=MILVUS_HOST, \n",
    "                                                                           port=MILVUS_PORT,\n",
    "                                                                           collection_name='molecular_search'))\n",
    "                   .map('smiles', 'smiless', lambda x: print(x))\n",
    "                   .output('res')\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "44dd76db",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "insert_pipe(df)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "1adbb2e1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total number of inserted data is 10000.\n"
     ]
    }
   ],
   "source": [
    "collection.flush()\n",
    "print('Total number of inserted data is {}.'.format(collection.num_entities))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e771112",
   "metadata": {},
   "source": [
    "#### Explanation of Data Processing Pipeline\n",
    "\n",
    "Here is detailed explanation for each line of the code:\n",
    "\n",
    "`.flat_map('df', ('smiles', 'id'), lambda df: df.values.tolist())`: read tabular data from the df and flatten(smiles and id columns);\n",
    "\n",
    "`.map('smiles', 'fp', ops.molecular_fingerprinting.rdkit(algorithm='daylight'))`: use the daylight algorithm to generate fingerprint with the [rdkit operator](https://towhee.io/molecular-fingerprinting/rdkit) in towhee hub;\n",
    "\n",
    "`.map(('id', 'fp'), 'res', ops.ann_insert.milvus_client(host='127.0.0.1', port='19530', collection_name='molecular_search'))`: insert molcular fingerprints in to Milvus;\n",
    "\n",
    "Then we decompose the pipeline into stages to get a more intuitive understanding of what's going on\n",
    "\n",
    "- read df data, get a list with two columns and flatten it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "c835ea92",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table style=\"border-collapse: collapse;\"><tr><th style=\"text-align: center; font-size: 130%; border: none;\">smiles</th> <th style=\"text-align: center; font-size: 130%; border: none;\">id</th></tr> <tr><td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">COc1cc(C=NNC(=O)CC2(C)OCCO2)cc(c1OCc1cccc2c1cccc2)Br</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">2860827</td></tr> <tr><td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">Fc1ccc(cc1)C=C1C(=O)NC(=S)N(C1=O)c1ccc(cc1Cl)Cl</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">2860828</td></tr> <tr><td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">O=C1NC(=C(C(N1)c1cccc(c1)Br)C(=O)Nc1ccc(cc1)Cl)C</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">2860829</td></tr></table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from towhee import DataCollection\n",
    "\n",
    "p1 = (pipe.input('df')\n",
    "          .flat_map('df', ('smiles', 'id'), lambda df: df.values.tolist())\n",
    "          .output('smiles', 'id')\n",
    "     )\n",
    "res = p1(df.head(3))\n",
    "DataCollection(res).show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "011ebd44",
   "metadata": {},
   "source": [
    "- generate fingerprint with `fp` column"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "148d08e7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table style=\"border-collapse: collapse;\"><tr><th style=\"text-align: center; font-size: 130%; border: none;\">smiles</th> <th style=\"text-align: center; font-size: 130%; border: none;\">id</th> <th style=\"text-align: center; font-size: 130%; border: none;\">fp</th></tr> <tr><td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">COc1cc(C=NNC(=O)CC2(C)OCCO2)cc(c1OCc1cccc2c1cccc2)Br</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">2860827</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">b'\\xa1\\x00\\x88#\\x067\\xb0\\xa9S\\xa2\\x8c8\\x8b\\xc25\\xe3\\x01\\x98b@G\\x8dQ\\x85\\xa6(\\xb1B  \\x16\\xa0`\\x00dFL\\x00\\x7f\\x93\\x02\\x8e\\x0b\\xe4\\...</td></tr> <tr><td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">Fc1ccc(cc1)C=C1C(=O)NC(=S)N(C1=O)c1ccc(cc1Cl)Cl</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">2860828</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">b\"\\xe64\\xf0\\t\\xd2\\xfbT\\x9eEt(,\\xbd\\x19\\xd3\\x82b\\xe3\\xe8%B\\xedx\\xd0\\xed\\xfa\\x8f\\xf6\\x95hM\\xb4\\xc1\\x14Q\\\\\\\\\\xe3\\x10\\x0f\\x89DW\\x94\\...</td></tr> <tr><td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">O=C1NC(=C(C(N1)c1cccc(c1)Br)C(=O)Nc1ccc(cc1)Cl)C</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">2860829</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">b'6O\\x1ci@\\xdan\\x85\\xd5\\xdb\\x88\\x19\\xcd:\\xd9\\x1ek\\x98\\xe1\\xe4_\\xafxc6\\xf2r\\xfb\\x86\\x8c\\xc3\\xfeg\\xd0\\xbb\\x08\\xdeW\\xf0\\xdf\\x15D\\x0...</td></tr></table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "p1 = (pipe.input('df')\n",
    "          .flat_map('df', ('smiles', 'id'), lambda df: df.values.tolist())\n",
    "          .map('smiles', 'fp', ops.molecular_fingerprinting.rdkit(algorithm='daylight'))\n",
    "          .output('smiles', 'id', 'fp')\n",
    "     )\n",
    "res = p1(df.head(3))\n",
    "DataCollection(res).show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a387639d",
   "metadata": {},
   "source": [
    "### Query Molecular from Milvus with Towhee"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bec378cd",
   "metadata": {},
   "source": [
    "Now that fingerprint for candidate SMILES have been inserted into Milvus, we can query across it. Again, we use Towhee to load the input SMILES, compute a fingerprint, and use it as a query in Milvus. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7f9bdc34",
   "metadata": {},
   "source": [
    "#### similarity search"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "56b38d6c",
   "metadata": {},
   "outputs": [],
   "source": [
    "search_pipe = (pipe.input('query_smiles')\n",
    "                   .map('query_smiles', 'fp', ops.molecular_fingerprinting.rdkit(algorithm='daylight'))\n",
    "                   .flat_map('fp', ('id', 'score', 'similar_smiles'), ops.ann_search.milvus_client(host=MILVUS_HOST, \n",
    "                                                                  port=MILVUS_PORT,\n",
    "                                                                  collection_name='molecular_search',\n",
    "                                                                  param={\"metric_type\": \"JACCARD\", \"nprobe\": 10},\n",
    "                                                                  output_fields=['smiles']))\n",
    "                   .output('query_smiles', 'similar_smiles')\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "085f9d04",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table style=\"border-collapse: collapse;\"><tr><th style=\"text-align: center; font-size: 130%; border: none;\">query_smiles</th> <th style=\"text-align: center; font-size: 130%; border: none;\">similar_smiles</th></tr> <tr><td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">CN1C=NC2=C1C(=O)N(C(=O)N2C)C</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">Cn1cnc2c1c(=O)n(C)c(=O)n2C</td></tr> <tr><td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">CN1C=NC2=C1C(=O)N(C(=O)N2C)C</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">Cn1c2nc[nH]c2c(=O)n(C)c1=O</td></tr> <tr><td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">CN1C=NC2=C1C(=O)N(C(=O)N2C)C</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">Cn1cnc2c1c(=O)[nH]c(=O)n2C</td></tr> <tr><td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">CN1C=NC2=C1C(=O)N(C(=O)N2C)C</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">Cn1cnc2[nH]c(=O)n(C)c(=O)c21</td></tr> <tr><td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">CN1C=NC2=C1C(=O)N(C(=O)N2C)C</td> <td style=\"text-align: center; vertical-align: center; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \">Cn1c2nc[nH]c2c(=O)[nH]c1=O</td></tr></table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "res = search_pipe('CN1C=NC2=C1C(=O)N(C(=O)N2C)C')\n",
    "DataCollection(res).show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e64cdd4a",
   "metadata": {},
   "source": [
    "If you want to show the molecular structure with images, you can use the `to_images` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "d1a41afd",
   "metadata": {},
   "outputs": [],
   "source": [
    "search_pipe1 = (pipe.input('query_smiles')\n",
    "                   .map('query_smiles', 'fp', ops.molecular_fingerprinting.rdkit(algorithm='daylight'))\n",
    "                   .flat_map('fp', ('id', 'score', 'similar_smiles'), ops.ann_search.milvus_client(host=MILVUS_HOST, \n",
    "                                                                  port=MILVUS_PORT,\n",
    "                                                                  collection_name='molecular_search',\n",
    "                                                                  limit=6,\n",
    "                                                                  param={\"metric_type\": \"JACCARD\", \"nprobe\": 10},\n",
    "                                                                  output_fields=['smiles']))\n",
    "                   .window_all('query_smiles', 'query_smiles', lambda x: to_images(x[:1]))\n",
    "                   .window_all('similar_smiles', 'similar_smiles', to_images)\n",
    "                   .output('query_smiles', 'similar_smiles')\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "f59fb5b7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table style=\"border-collapse: collapse;\"><tr><th style=\"text-align: center; font-size: 130%; border: none;\">query_smiles</th> <th style=\"text-align: center; font-size: 130%; border: none;\">similar_smiles</th></tr> <tr><td style=\"text-align: center; vertical-align: top; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \"><img src=\"\" 128 = \"128px\" 128 = \"128px\" style = \"float:left; padding:2px\"></td> <td style=\"text-align: center; vertical-align: top; border-right: solid 1px #D3D3D3; border-left: solid 1px #D3D3D3; \"><img src=\"\" 128 = \"128px\" 128 = \"128px\" style = \"float:left; padding:2px\"> <img src=\"\" 128 = \"128px\" 128 = \"128px\" style = \"float:left; padding:2px\"> <img src=\"\" 128 = \"128px\" 128 = \"128px\" style = \"float:left; padding:2px\"> <img src=\"\" 128 = \"128px\" 128 = \"128px\" style = \"float:left; padding:2px\"> <img src=\"\" 128 = \"128px\" 128 = \"128px\" style = \"float:left; padding:2px\"> <img src=\"\" 128 = \"128px\" 128 = \"128px\" style = \"float:left; padding:2px\"></td></tr></table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "res = search_pipe1('Cn1ccc(=O)nc1')\n",
    "DataCollection(res).show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42c37993",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
